package cn.huanzi.qch.springbootmybatis.service;


import cn.huanzi.qch.springbootmybatis.constant.Constant;
import cn.huanzi.qch.springbootmybatis.exception.ValidateErrorException;
import cn.huanzi.qch.springbootmybatis.mapper.SettingMapper;
import cn.huanzi.qch.springbootmybatis.mapper.SysDbBackupMapper;
import cn.huanzi.qch.springbootmybatis.model.Setting;
import cn.huanzi.qch.springbootmybatis.model.SysDbBackup;
import cn.huanzi.qch.springbootmybatis.util.ZipUtils;
import org.slf4j.Logger;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Service;
import org.springframework.util.ObjectUtils;

import javax.annotation.Resource;
import java.io.*;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.zip.ZipEntry;
import java.util.zip.ZipOutputStream;


@Service
public class MysqlDumpService {
    private final static Logger logger = org.slf4j.LoggerFactory.getLogger(MysqlDumpService.class);

    @Value("${spring.datasource.url}")
    private String databaseUrl;
    @Value("${spring.datasource.username}")
    private String databaseUsername;
    @Value("${spring.datasource.password}")
    private String databasePwd;
    @Resource
    private SettingMapper settingMapper;
    @Resource
    private SysDbBackupMapper sysDbBackupMapper;


    /**
     * 执行生成备份
     */
    public boolean doBackup(String operatorId, String backupType) {
        Runtime runtime = Runtime.getRuntime();  //获取Runtime实例
        String databaseStr = databaseUrl.substring(0, databaseUrl.indexOf("?"));
        String database = databaseStr.substring(databaseStr.lastIndexOf("/") + 1, databaseStr.length()); // 需要备份的数据库名
        String port = databaseStr.substring(databaseStr.lastIndexOf(":") + 1, databaseStr.lastIndexOf("/")); // 需要备份的数据库端口号
        String host = databaseStr.substring(databaseStr.substring(0, databaseStr.lastIndexOf(":")).lastIndexOf("/") + 1, databaseStr.lastIndexOf(":")); // 需要备份的数据库IP
        Date currentDate = new Date();
        SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-ddHH-mm-ss");
        String sdfDate = sdf.format(currentDate);
        //获取数据库备份时的默认位置
        Setting setting = settingMapper.selectByType(Constant.SETTING_MYSQLDUMP_PATH);
        String filename = "unicorn_" + sdfDate;
        String path = !ObjectUtils.isEmpty(setting) ? setting.getValue() : "d:\\"; //备份的路径地址
        File file = new File(path);
        if (!file.exists()) {
            file.mkdirs();
        }
        String filepath = path + filename;
        String sqlFilepath = filepath + ".sql";
        String zipFilepath = filepath + ".zip";

        //存数据库备份记录,是备份中的状态
        SysDbBackup sysDbBackup = new SysDbBackup();
        sysDbBackup.setSqlPath(zipFilepath);
        sysDbBackup.setOperatorId(operatorId);
        sysDbBackup.setBackupType(backupType);
        sysDbBackupMapper.insert(sysDbBackup);

        //执行命令
        //获取服务器中数据库的bin目录及my.ini所在的位置
        Setting binSetting = settingMapper.selectByType(Constant.SETTING_MYSQL_BINPATH);
        if (!ObjectUtils.isEmpty(binSetting)) {
            StringBuilder stringBuilder = new StringBuilder();
            stringBuilder.append("mysqldump --master-data").append(" --opt").append(" -h").append(host).append(" -P").append(port);
            stringBuilder.append(" --user=").append(databaseUsername).append(" --password=").append(databasePwd)
                    .append(" --lock-all-tables=true").append(" --routines").append(" --triggers");
            stringBuilder.append(" --result-file=").append(sqlFilepath).append(" --default-character-set=utf8 ")
                    .append(database);
            logger.info("备份数据命令：{}", stringBuilder.toString());
            try {
                String[] command = {"cmd", "/c", stringBuilder.toString()};
                Process process = runtime.exec(command);
                read(process.getInputStream(), System.out);
                //若有错误信息则输出
                read(process.getErrorStream(), System.err);
                if (process.waitFor() == 0) {
                    compress(sqlFilepath, zipFilepath);

                    updateDbBackUpStatus(sysDbBackup,1);

                    return true;
                }
            } catch (Exception e) {
                logger.info("备份数据异常，时间为：{}", sdfDate);
                e.printStackTrace();
            }
        } else {
            throw new ValidateErrorException("mysqldump.pleaseMaintainMysqlUrl");
        }
        //更新操作失败时的状态并将空文件进行删除
        updateDbBackUpStatus(sysDbBackup,0);

        File sqlFile = new File(sqlFilepath);
        sqlFile.delete();
        return false;
    }


    private void updateDbBackUpStatus(SysDbBackup sysDbBackup,Integer status) {
        //更新操作失败时的状态并将空文件进行删除
        sysDbBackup.setStatus(status.toString());
        sysDbBackupMapper.update(sysDbBackup);
    }


    // 读取输入流
    private void read(InputStream inputStream, PrintStream out) {
        try {
            BufferedReader reader = new BufferedReader(new InputStreamReader(inputStream, "gbk"));

            String line;
            while ((line = reader.readLine()) != null) {
                out.println(line);
            }

        } catch (IOException e) {
            e.printStackTrace();
        } finally {

            try {
                inputStream.close();
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }

    //压缩及将源文件删除
    private void compress(String srcFilePath, String destFilePath) {
        File src = new File(srcFilePath);

        if (!src.exists()) {
            throw new RuntimeException(srcFilePath + "不存在");
        }
        File zipFile = new File(destFilePath);
        try {
            FileOutputStream fos = new FileOutputStream(zipFile);
            ZipOutputStream zos = new ZipOutputStream(fos);
            String baseDir = "";
            compressFile(src, zos, baseDir);
            src.delete();
            zos.close();
        } catch (Exception e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        }
    }

    /**
     * 压缩文件
     */
    private void compressFile(File file, ZipOutputStream zos, String baseDir) {
        if (!file.exists())
            return;
        try {
            BufferedInputStream bis = new BufferedInputStream(new FileInputStream(file));
            ZipEntry entry = new ZipEntry(baseDir + file.getName());
            zos.putNextEntry(entry);
            int count;
            byte[] buf = new byte[1024];
            while ((count = bis.read(buf)) != -1) {
                zos.write(buf, 0, count);
            }
            bis.close();
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    public boolean recoverDB(SysDbBackup sysDbBackup) {
        File saveFile = new File(sysDbBackup.getSqlPath());
        if (!saveFile.exists()) {// 如果目录不存在
            logger.info("数据库恢复失败,备份文件不存在，请检查");
            throw new ValidateErrorException("mysqldump.urlNotExists");
        }
        String sqlPath = sysDbBackup.getSqlPath().replace("zip", "sql");

        //获取数据库备份时的默认位置
        Setting setting = settingMapper.selectByType(Constant.SETTING_MYSQLDUMP_PATH);
        ZipUtils.unZip(saveFile, setting.getValue());
        try {
            String databaseStr = databaseUrl.substring(0, databaseUrl.indexOf("?"));
            String database = databaseStr.substring(databaseStr.lastIndexOf("/") + 1, databaseStr.length()); //需要备份的数据库名
            String port = databaseStr.substring(databaseStr.lastIndexOf(":") + 1, databaseStr.lastIndexOf("/")); // 需要备份的数据库端口号
            String host = databaseStr.substring(databaseStr.substring(0, databaseStr.lastIndexOf(":")).lastIndexOf("/") + 1, databaseStr.lastIndexOf(":")); // 需要备份的数据库IP
            String statement = "mysql -h" + host + " -P" + port + " -u" + databaseUsername + " -p" + databasePwd + " --default-character-set=utf8 " + database + "<" + sqlPath;
            String[] cmd = {"cmd", "/c", statement};
            Process process = Runtime.getRuntime().exec(cmd);
            BufferedReader br = new BufferedReader(new InputStreamReader(process.getErrorStream(), "utf-8"));
            String errorLine;
            while ((errorLine = br.readLine()) != null) {
                System.out.println(errorLine);
            }
            br.close();
            if (process.waitFor() == 0) {// 0 表示线程正常终止。
                logger.info("数据库恢复成功");
                File src = new File(sqlPath);
                src.delete();
                return true;
            }
        } catch (Exception e) {
            e.printStackTrace();
            logger.info("数据库恢复失败");
        }
        return false;
    }
}
